/*
* "PS2" Application Framework
*
* University of Abertay Dundee
* May be used for educational purposed only
*
* Author - Dr Henry S Fortuna
*
* $Revision: 1.2 $
* $Date: 2007/08/19 12:45:09 $
*
*/

// A DMA class. This should prove extremely useful, and frees you from the
// irritating task of having to count quad word counts which should mean your
// crash count goes down a lot.

// There are two types of DMA chains here. A static DMA chain, and a dynamic one.
// The static one has data added to it at initialisation time and is never changed
// again. The dynamic one is built every frame and is double buffered so that the
// next frame's DMA packet can be built while the current frame's one is being
// processed. This means that we no longer need to send the packet, and wait right away.
// We can instead send the packet, build the new one, and then wait.
// This is exploiting parallelism and will make everything much faster, especially
// when you start doing non-trivial things. A key to speed on the PS2 is to only add
// what you need per frame, so any data that won't change per frame should go into
// the static DMA chain, and then be called from the dynamic chain using a DMA call tag.

#ifndef __DMA_H__
#define __DMA_H__

#include "sps2wrap.h"
#include "singleton.h"
#include "ps2matrix4x4.h"
#include "ps2vector4.h"
#include "PS2Defines.h"

// These macros allow you to treat VIFStaticDMA and
// VIFDynamicDMA as if they were global variables. Much easier!
#define VIFStaticDMA CVIFStaticDMA::GetSingleton()
#define VIFDynamicDMA CVIFDynamicDMA::GetSingleton()

// A base class for the static and dynamic chains. This class is never
// used alone, it just holds all of the behaviour that is common to both the other classes.
class CDMAChain {
public:
    // Constructor
    CDMAChain() {
        // Zero the array that will hold all of the SPS2 memory pages
        m_pPages = 0;
    }

    // Destructor
    virtual ~CDMAChain() {
        // Delete the array that holds the SPS2 memory pages (note we aren't
        // freeing up the SPS2 memory, just this array)
        if (m_pPages) {
            delete [] m_pPages;
            m_pPages = 0;
        }
    }

    void Begin();				// Starts a new DMA chain

    // This is the most important function of the whole class. It adds
    // 32bits (an int worth) of data to the chain, and handles stitching
    // automatically. All of the other add-data functions use this.
    void Add32(uint32 data) {
        if (m_pPtr >= m_pEndPtr) {	// Would we be writing to the new page?
            Stitch();			// If so then stitch
        }

        *m_pPtr++ = data;		// Write the data and then move the pointer on
    }

    // These are simply to make life easier, as you can see this function just calls
    // Add32 twice.
    void Add64(uint64 data) {
        Add32((uint32)data);
        Add32((uint32)(data >> 32));
    }

    void Add128(uint128 data) {
        Add64((uint64)data);
        Add64((uint64)(data >> 64));
    }

    void AddFloat(float data) {
        Add32(*(uint32 *)&data);
    }

    void AddVectorI(int x, int y, int z, int w);
    void AddVectorF(float x, float y, float z, float w);

    void AddVector(const Vector4 & Vector) {
        AddFloat(Vector.x);
        AddFloat(Vector.y);
        AddFloat(Vector.z);
        AddFloat(Vector.w);
    }

    void AddMatrix(const Matrix4x4 & Matrix);

    void Align(int iAlign, int iOffset = 0);	// Aligns the write pointer to a mulitple of iAlign * 32 bits
    // The value can be offset by iOffset * 32 bits

    void PrepForDMATag();		// Makes sure that we are writing a multiple of 1 quadword (pad with No Operations
    // if we aren't) and also makes sure that the next tag isn't on a stitching boundary

    // Functions to add different DMA Tags (note that the class handles all of the QWC stuff, so you
    // don't need to worry!)
    void DMAEnd();
    // Use this function at the end of any static blocks to tell the DMAC to return to the dynamic buffer just
    // after the DMACall tag.
    void DMARet();
    // Use this to call little chunks inside the static DMA buffer. Use the physical address
    // returned by CVIFStaticDMA::GetPointer()
    void DMACall(uint32 iAddr);

    // Start a section of data that should be sent directly to the GS (i.e. GIF path 2)
    // These functions can go over a stitching boundary, so there is no need to worry about that.
    // They also handle calculating the QWC for the DIRECT VIFCode and will automatically align
    // to the last word of a quad if needed too!
    void StartDirect();
    void EndDirect();

    // Use these functions to add data that will used in A+D mode, and these functions
    // will calculate the QWC for you. (You must be in DIRECT mode to use these)
    void StartAD();
    void EndAD() {};
    void AddAD(uint64 iData, uint64 iAddr);

    // Use these functions to transfer microcode across to VU1 via the DMAC
    void StartMPG(uint32 iAddr);
    void EndMPG();
    void AddMPG(uint64 iInst);

    // Add a VIF unpack code. You must know the number of quadwords you are adding for this function.
    void AddUnpack(int format, int addr, int num, int usetops = 0, int nosign = 1, int masking = 0) {
        Add32((0x60 << 24) + (format << 24) + (masking << 28) + (usetops << 15) +
              (nosign << 14) + (num << 16) + addr);
    }

    // A nice function for debugging that will print the contents of the packet
    // out via printf
    virtual void PrintPacket();

protected:
    virtual int NewPage() = 0;	// Starts writing to a new memory page
    virtual void Stitch();		// Stitches across the page boundary with a next tag
    virtual void NewTag();		// Reserves space in the chain for a DMA tag

    int m_iNumPages;			// How many pages we have allocated for DMAing
    CDMAMem * m_pPages;			// All the pages that we have allocated

    int * m_pPtr;				// The data write pointer
    int * m_pEndPtr;			// The next page starts here
    int m_iPhysAddr;			// The physical address of the first page (for TADR)

    uint64 * m_pDMATag;			// The currently open DMA tag
    int * m_pVIFCode;			// The currently open VIF code

    int m_iMPGCount;			// How many MPG instructions we have written
    int m_iMPGAddr;				// The VU_MICRO_MEM address we are writing to

    uint64 * m_pADGiftag;		// The currently open AD GIFtag.

    typedef enum {
        VIF_BASE,
        VIF_DIRECT				// Direct + TTE has alignment issues, MPG, and unpack do not
    } VIF1_STATE;				// The different states the DMA chain can be in

    VIF1_STATE m_VifState;		// The VIF state at this point in the chain
};

// The static DMA chain. You should use this by first getting the physical address via GetPointer
// You should then save this address to use with a DMACall tag in the dynamic chain. Then add your
// data in the standard CDMAChain way. Finally add a DMARet tag to the static DMA chain so that
// the DMAC will return control to the dynamic chain.
class CVIFStaticDMA : public CDMAChain, public CSingleton<CVIFStaticDMA> {
public:
    CVIFStaticDMA();
    virtual ~CVIFStaticDMA();

    void Initialise(int iNumPages);

    int GetPointer();			// Retrieve the current pointer's physical address

protected:
    virtual int NewPage();

    int m_iCurrPage;
    int m_iPhysicalOffset;
};

inline int CVIFStaticDMA::GetPointer() {
    return (int)m_pDMATag + m_iPhysicalOffset;
}

// The dynamic DMA chain is the only DMA chain that you should fire. You should fire this at
// the start of your main loop, then add all of your data to the class. That way while the previous
// frame's data is being transferred, you can be building the next packet in parallel.
// This DMA chain is double buffered to allow this, so you should remember that when you call
// Initialise, this class will actually use double that, so make sure to allocate enough memory!
class CVIFDynamicDMA : public CDMAChain, public CSingleton<CVIFDynamicDMA> {
public:
    CVIFDynamicDMA();
    virtual ~CVIFDynamicDMA();

    // See comment above
    void Initialise(int iNumPages);
    // This fires the packet via path 1. It takes two parameters, one that allows you
    // to build but not actually fire the packet (good for debugging), and the second which
    // will print the packet out via printf.
    void Fire(bool bFire = true, bool bPrint = false);

    // Prints the packet via printf
    virtual void PrintPacket();

    // Retrieve a temporary piece of SPS2 memory. Note that this returns the
    // First page from the buffer that is currently being sent, so don't call this
    // Directly after calling Fire or you could experience problems.
    CDMAMem & getTempPage(int iPage = 0) {
        return m_pPages[((m_iCurrBuffer ^ 1) * m_iNumPages) + iPage];
    }

protected:
    virtual int NewPage();

    int m_iCurrPage;	// The current page we are writing to.
    int m_iCurrBuffer;	// The current buffer we are writing to (0 or 1)
};

#endif
